﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Linq;
using SharpAnalyzer.Entities;
using SharpAnalyzer.Utilities;
using SharpAnalyzer.Collectors;

namespace SharpAnalyzer.Calculators
{
    internal sealed class NamespaceMetricsCalculator
    {
        public static void Calculate(Microsoft.CodeAnalysis.Solution solution, Microsoft.CodeAnalysis.Compilation compilation, ref Namespace namespaceMetric)
        {
            if (!namespaceMetric.SyntaxNodes.Any())
                return;

            Entities.Type[] typeMetrics = GetTypeDeclarations(namespaceMetric);
            for (int cnt = 0; cnt < typeMetrics.Length; cnt++)
            {
                var typeMetric = typeMetrics[cnt];

                TypeMetricsCalculator typeMetricsCalculator = new TypeMetricsCalculator(solution, compilation);
                typeMetricsCalculator.Calculate(ref typeMetric);
            }

            // Calculate Internal Metrics
            IEnumerable<string> sourceCC = CalculateClassCoupling(typeMetrics);
            namespaceMetric.Types = typeMetrics.ToList();
            namespaceMetric.Metrics = new List<Metric>()
            {
                new Metric()
                {
                    Name = MetricNames.MaintainabilityIndex,
                    Value = CalculateAveMaintainabilityIndex(typeMetrics),
                },
                new Metric()
                {
                    Name = MetricNames.CyclomaticComplexity,
                    Value = CalculateCyclomaticComplexity(typeMetrics),
                },
                new ClassCouplingMetricResult()
                {
                    Name = MetricNames.ClassCoupling,
                    Value = sourceCC.Count(),
                    Types = sourceCC,
                },
                new Metric()
                {
                    Name = MetricNames.DepthOfInheritance,
                    Value = CalculateDepthOfInheritance(typeMetrics),
                },
                new Metric()
                {
                    Name = MetricNames.LinesOfCode,
                    Value = CalculateLinesOfCode(typeMetrics),
                },
                new Metric()
                {
                    Name = MetricNames.NumberOfTypes,
                    Value = typeMetrics.Count(),
                },
                new Metric()
                {
                    Name = MetricNames.NumberOfMethods,
                    Value = CalculateNumberOfMethods(typeMetrics),
                }
            };
        }

        private static Entities.Type[] GetTypeDeclarations(Namespace namespaceDeclaration)
        {
            List<CodeSectionDeclarationSyntaxInfo> allTypes = new List<CodeSectionDeclarationSyntaxInfo>();
            foreach (CodeSectionDeclarationSyntaxInfo namespaceNode in namespaceDeclaration.SyntaxNodes)
            {
                var types = new TypeCollectorSyntaxWalker()
                    .GetTypes(namespaceNode.Syntax)
                    .Select(x => new CodeSectionDeclarationSyntaxInfo()
                    {
                        Name = x.GetName(x.SyntaxTree.GetRoot()),
                        CodeFile = namespaceNode.CodeFile,
                        Syntax = x,
                    });
                allTypes.AddRange(types);
            }

            // Group all the types from previous step into by Name
            var retval = allTypes
                .GroupBy(x => x.Name)
                .Select(x => new Entities.Type()
                {
                    Name = x.Key,
                    SyntaxNodes = x.ToList().AsReadOnly()
                })
                .ToArray();
            return retval;
        }

        #region Accumulate Results
        private static int CalculateNumberOfMethods(IEnumerable<Entities.Type> metrics)
        {
            var source = from typeMetric in metrics
                         select typeMetric.Members.Count();
            return source.Sum();
        }

        private static IEnumerable<string> CalculateClassCoupling(IEnumerable<Entities.Type> metrics)
        {
            var source = from typeMetric in metrics
                         from metric in typeMetric.Metrics.OfType<ClassCouplingMetricResult>()
                         from type in metric.Types
                         select type;
            return source.Distinct().OrderBy(x => x);
        }

        private static double CalculateAveMaintainabilityIndex(IEnumerable<Entities.Type> typeMetrics)
        {
            var source = from typeMetric in typeMetrics
                         from metric in typeMetric.Metrics
                         where metric.Name == MetricNames.MaintainabilityIndex
                         select (double)metric.Value;
            if (source.Any())
                return Math.Round(source.Average());
            return 100.0;
        }

        private static int CalculateCyclomaticComplexity(IEnumerable<Entities.Type> typeMetrics)
        {
            var source = from typeMetric in typeMetrics
                         from metric in typeMetric.Metrics
                         where metric.Name == MetricNames.CyclomaticComplexity
                         select (int)metric.Value;
            return source.Sum();
        }

        private static int CalculateDepthOfInheritance(IEnumerable<Entities.Type> typeMetrics)
        {
            var source = from typeMetric in typeMetrics
                         from metric in typeMetric.Metrics
                         where metric.Name == MetricNames.DepthOfInheritance
                         select (int)metric.Value;
            return source.Max();
        }

        private static int CalculateLinesOfCode(IEnumerable<Entities.Type> typeMetrics)
        {
            var source = from typeMetric in typeMetrics
                         from metric in typeMetric.Metrics
                         where metric.Name == MetricNames.LinesOfCode
                         select (int)metric.Value;
            return source.Sum();
        }
        #endregion
    }
}
